#include <string.h>
#include "sdkconfig.h"
#include "esp_check.h"
#include "esp_vfs_fat.h"
#include "driver/i2s_tdm.h"
#include "driver/i2c.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "es7210.h"
#include "format_wav.h"

/* I2C port and GPIOs */
#define I2C_NUM            (0)
#define I2C_SDA_IO         (1)
#define I2C_SCL_IO         (2)

/* I2S port and GPIOs */
#define I2S_NUM             0
#define I2S_MCK_IO          38
#define I2S_BCK_IO          14
#define I2S_WS_IO           13
#define I2S_DI_IO           12

/* SD card GPIOs */
#define SD_CMD_IO           48
#define SD_CLK_IO           47
#define SD_DAT0_IO          21

/* I2S configurations */
#define I2S_TDM_FORMAT             (ES7210_I2S_FMT_I2S)
#define I2S_CHAN_NUM               (2)
#define I2S_SAMPLE_RATE            (48000)
#define I2S_MCLK_MULTIPLE          (I2S_MCLK_MULTIPLE_256)
#define I2S_SAMPLE_BITS            (I2S_DATA_BIT_WIDTH_16BIT)
#define I2S_TDM_SLOT_MASK          (I2S_TDM_SLOT0 | I2S_TDM_SLOT1)

/* ES7210 configurations */
#define ES7210_I2C_ADDR             (0x41)
#define ES7210_I2C_CLK              (100000)
#define ES7210_MIC_GAIN             (ES7210_MIC_GAIN_30DB)
#define ES7210_MIC_BIAS             (ES7210_MIC_BIAS_2V87)
#define ES7210_ADC_VOLUME           (0)

/* SD card & recording configurations */
#define RECORD_TIME_SEC             (10)
#define SD_MOUNT_POINT              "/sdcard"
#define RECORD_FILE_PATH            "/RECORD.WAV"

static const char *TAG = "ES7210";
/**
 * @brief es7210_i2s_init
 * @note es7210初始化函数
 * @return i2s_chan_handle_t 
 */
static i2s_chan_handle_t es7210_i2s_init(void)
{
    i2s_chan_handle_t i2s_rx_chan = NULL;
    ESP_LOGI(TAG, "Create I2S receive channel");
    i2s_chan_config_t i2s_rx_conf = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);      //使用默认配置初始化I2S通道配置结构体，I2S_NUM_AUTO表示自动选择I2S控制器，I2S_ROLE_MASTER表示作为主设备
    ESP_ERROR_CHECK(i2s_new_channel(&i2s_rx_conf, NULL, &i2s_rx_chan));                             //创建新的I2S接收通道

    ESP_LOGI(TAG, "Configure I2S receive channel to TDM mode");
    i2s_tdm_config_t i2s_tdm_rx_conf = {
        //时隙配置：使用Philips标准时隙配置
        //参数包括采样位数、立体声模式和时隙掩码
        .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_SAMPLE_BITS, I2S_SLOT_MODE_STEREO, I2S_TDM_SLOT_MASK),
        .clk_cfg  = {
            //时钟配置
            .clk_src = I2S_CLK_SRC_DEFAULT,             //使用默认时钟源
            .sample_rate_hz = I2S_SAMPLE_RATE,          //设置采样率
            .mclk_multiple = I2S_MCLK_MULTIPLE          //设置主时钟倍数
        },
        //GPIO引脚配置
        .gpio_cfg = {
            .mclk = I2S_MCK_IO,                         //主时钟引脚
            .bclk = I2S_BCK_IO,                         //位时钟引脚
            .ws   = I2S_WS_IO,                          //字选择(左右时钟)引脚
            .dout = -1,                                 // ES7210 only has ADC capability
            .din  = I2S_DI_IO                           //数据输入引脚
        },
    };

    ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(i2s_rx_chan, &i2s_tdm_rx_conf));      //初始化配置I2S通道为TDM模式

    return i2s_rx_chan;
}

/**
 * @brief es7210_codec_init
 * 
 */
static void es7210_codec_init(void)
{
    ESP_LOGI(TAG, "Init I2C used to configure ES7210");
    //配置I2C参数
    i2c_config_t i2c_conf = {
        .sda_io_num = I2C_SDA_IO,                       //设置SDA数据线引脚
        .scl_io_num = I2C_SCL_IO,                       //设置SCL时钟线引脚
        .mode = I2C_MODE_MASTER,                        //设置I2C工作模式为主模式
        .sda_pullup_en = GPIO_PULLUP_ENABLE,            //使能SDA引脚上拉电阻
        .scl_pullup_en = GPIO_PULLUP_ENABLE,            //使能SCL引脚上拉电阻
        .master.clk_speed = ES7210_I2C_CLK,             //设置I2C时钟频率
    };
    ESP_ERROR_CHECK(i2c_param_config(I2C_NUM, &i2c_conf));      //初始化I2C控制器
    ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM, i2c_conf.mode, 0, 0, 0));   

    /* Create ES7210 device handle */
    es7210_dev_handle_t es7210_handle = NULL;
    //配置ES7210的I2C连接参数
    es7210_i2c_config_t es7210_i2c_conf = {
        .i2c_port = I2C_NUM,                            //指定使用的I2C端口号
        .i2c_addr = ES7210_I2C_ADDR                     //指定ES7210的I2C设备地址
    };
    ESP_ERROR_CHECK(es7210_new_codec(&es7210_i2c_conf, &es7210_handle));    //创建新的ES7210编解码器实例，获取设备句柄

    ESP_LOGI(TAG, "Configure ES7210 codec parameters");
    //配置ES7210编解码器的音频参数
    es7210_codec_config_t codec_conf = {
        .i2s_format = I2S_TDM_FORMAT,                   //设置I2S数据格式
        .mclk_ratio = I2S_MCLK_MULTIPLE,                //设置主时钟倍率
        .sample_rate_hz = I2S_SAMPLE_RATE,              //设置采样率
        .bit_width = (es7210_i2s_bits_t)I2S_SAMPLE_BITS,//设置采样位宽
        .mic_bias = ES7210_MIC_BIAS,                    //设置麦克风偏置电压
        .mic_gain = ES7210_MIC_GAIN,                    //设置麦克风增益
        .flags.tdm_enable = true                        //启用TDM模式
    };
    ESP_ERROR_CHECK(es7210_config_codec(es7210_handle, &codec_conf));
    ESP_ERROR_CHECK(es7210_config_volume(es7210_handle, ES7210_ADC_VOLUME));    //配置ES7210的ADC音量
}

/**
 * @brief mount_sdcard
 * @note 
 * @return sdmmc_card_t* 返回指向sdmmc_card_t结构体的指针，用于表示已挂载的SD卡
 */
sdmmc_card_t * mount_sdcard(void)
{
    sdmmc_card_t *sdmmc_card = NULL;

    ESP_LOGI(TAG, "Mounting SD card");
    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = true,                             //若挂载失败，是否格式化SD卡
        .max_files = 2,                                             //同时打开最大文件数
        .allocation_unit_size = 8 * 1024                            //分配单元大小
    };
    ESP_LOGI(TAG, "Initializing SD card using SDMMC peripheral");

    sdmmc_host_t sdmmc_host = SDMMC_HOST_DEFAULT();                 //SDMMC主机接口配置
    sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();  //SDMMC插槽配置
    slot_config.width = 1;                                          //设置为1线SD模式
    slot_config.clk = SD_CLK_IO;
    slot_config.cmd = SD_CMD_IO;
    slot_config.d0 = SD_DAT0_IO;
    slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;           //打开内部上拉电阻

    esp_err_t ret;
    while (1) {
        ret = esp_vfs_fat_sdmmc_mount(SD_MOUNT_POINT, &sdmmc_host, &slot_config, &mount_config, &sdmmc_card);
        if (ret == ESP_OK) {
            break;
        } else if (ret == ESP_FAIL) {
            ESP_LOGE(TAG, "Failed to mount filesystem.");
        } else {
            ESP_LOGE(TAG, "Failed to initialize the card (%s). "
                     "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }

    ESP_LOGI(TAG, "Card size: %lluMB, speed: %dMHz",
            (((uint64_t)sdmmc_card->csd.capacity) * sdmmc_card->csd.sector_size) >> 20,
            sdmmc_card->max_freq_khz / 1000);

    return sdmmc_card;
}

/**
 * @brief record_wav
 * 
 * @param i2s_rx_chan 
 * @return esp_err_t 
 */
static esp_err_t record_wav(i2s_chan_handle_t i2s_rx_chan)
{
    ESP_RETURN_ON_FALSE(i2s_rx_chan, ESP_FAIL, TAG, "invalid i2s channel handle pointer");
    esp_err_t ret = ESP_OK;

    uint32_t byte_rate = I2S_SAMPLE_RATE * I2S_CHAN_NUM * I2S_SAMPLE_BITS / 8;              //计算每秒字节数(字节率) = 采样率*通道数*每个采样位数/8
    uint32_t wav_size = byte_rate * RECORD_TIME_SEC;                                        //计算整个WAV文件的数据部分大小

    const wav_header_t wav_header =
        WAV_HEADER_PCM_DEFAULT(wav_size, I2S_SAMPLE_BITS, I2S_SAMPLE_RATE, I2S_CHAN_NUM);   //默认配置WAV文件头部信息，包括文件大小，采样位数，采样率，通道数

    ESP_LOGI(TAG, "Opening file %s", RECORD_FILE_PATH);
    FILE *f = fopen(SD_MOUNT_POINT RECORD_FILE_PATH, "w");
    ESP_RETURN_ON_FALSE(f, ESP_FAIL, TAG, "error while opening wav file");

    /* Write wav header */
    ESP_GOTO_ON_FALSE(fwrite(&wav_header, sizeof(wav_header_t), 1, f), ESP_FAIL, err,
                      TAG, "error while writing wav header");

    /* Start recording */
    size_t wav_written = 0;
    static int16_t i2s_readraw_buff[4096];                                                  //定义静态缓存区，用于存储I2S读取的原始数据
    ESP_GOTO_ON_ERROR(i2s_channel_enable(i2s_rx_chan), err, TAG, "error while starting i2s rx channel");    //启用I2S接收通道，若失败，跳转到err
    while (wav_written < wav_size) {                                                        //当写入的字节数未达到预期WAV数据大小就继续循环
        if(wav_written % byte_rate < sizeof(i2s_readraw_buff)) {
            ESP_LOGI(TAG, "Recording: %"PRIu32"/%ds", wav_written/byte_rate + 1, RECORD_TIME_SEC);
        }
        size_t bytes_read = 0;
        /* Read RAW samples from ES7210 */
        ESP_GOTO_ON_ERROR(i2s_channel_read(i2s_rx_chan, i2s_readraw_buff, sizeof(i2s_readraw_buff), &bytes_read,    //读取I2S通道数据
                          pdMS_TO_TICKS(1000)), err, TAG, "error while reading samples from i2s");
        /* Write the samples to the WAV file */
        ESP_GOTO_ON_FALSE(fwrite(i2s_readraw_buff, bytes_read, 1, f), ESP_FAIL, err,                                //将读取到的数据写入文件，若写入失败，跳转到err
                          TAG, "error while writing samples to wav file");
        wav_written += bytes_read;                                                          //更新已写入的字节数
    }

err:
    i2s_channel_disable(i2s_rx_chan);
    ESP_LOGI(TAG, "Recording done! Flushing file buffer");
    fclose(f);

    return ret;
}

void app_main(void)
{
    /* Init I2C bus to configure ES7210 and I2S bus to receive audio data from ES7210 */
    i2s_chan_handle_t i2s_rx_chan = es7210_i2s_init();
    /* Create ES7210 device handle and configure codec parameters */
    es7210_codec_init();
    /* Mount SD card, the recorded audio file will be saved into it */
    sdmmc_card_t *sdmmc_card = mount_sdcard();
    /* Start to record wav audio */
    esp_err_t err = record_wav(i2s_rx_chan);
    /* Unmount SD card */
    esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, sdmmc_card);
    if(err == ESP_OK) {
        ESP_LOGI(TAG, "Audio was successfully recorded into "RECORD_FILE_PATH
                      ". You can now remove the SD card safely");
    } else {
        ESP_LOGE(TAG, "Record failed, "RECORD_FILE_PATH" on SD card may not be playable.");
    }
}